import { Point, ObservablePoint, Rectangle } from '../math';
import { sign, TextureCache } from '../utils';
import { BLEND_MODES } from '../const';
import Texture from '../textures/Texture';
import DisplayObject from '../display/DisplayObject';

const tempPoint = new Point();

/**
 * The Sprite object is the base for all textured objects that are rendered to the screen
 *
 * A sprite can be created directly from an image like this:
 *
 * ```js
 * let sprite = new PIXI.Sprite.from_image('assets/image.png');
 * ```
 *
 * @class
 * @extends PIXI.DisplayObject
 * @memberof PIXI
 */
export default class Sprite extends DisplayObject
{
    /**
     * @param {PIXI.Texture|PIXI.Texture[]|PIXI.Sprite} texture - The texture for this sprite
     */
    constructor(texture)
    {
        super();

        /**
         * 当前帧
         *
         * @private
         * @member {number}
         */
        this._current_frame = -1;

        /**
         * The textures that the sprite is using
         *
         * @private
         * @member {PIXI.Texture[]}
         */
        this._textures = [];

        var newframe = 0;
        if(texture){
            if(texture instanceof Texture){
                this._textures.push(texture);
            }else if(Array.isArray(texture) && (texture[0] instanceof Texture)){
                this._textures = texture;
            }else if(texture instanceof Sprite){
                this._textures = texture._textures;
                newframe = texture.current_frame;
            }
        }

        /**
         * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect.
         *
         * @private
         * @member {number}
         * @default 0xFFFFFF
         */
        this._tint = null;
        this._tintRGB = null;
        this.tint = 0xFFFFFF;

        /**
         * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode.
         *
         * @member {number}
         * @default PIXI.BLEND_MODES.NORMAL
         * @see PIXI.BLEND_MODES
         */
        this.blend_mode = BLEND_MODES.NORMAL;

        /**
         * The shader that will be used to render the sprite. Set to null to remove a current shader.
         *
         * @member {PIXI.Filter|PIXI.Shader}
         */
        this.shader = null;

        /**
         * An internal cached value of the tint.
         *
         * @private
         * @member {number}
         * @default 0xFFFFFF
         */
        this.cachedTint = 0xFFFFFF;

        /**
         * this is used to store the vertex data of the sprite (basically a quad)
         *
         * @private
         * @member {Float32Array}
         */
        this.vertexData = new Float32Array(8);

        /**
         * This is used to calculate the bounds of the object IF it is a trimmed sprite
         *
         * @private
         * @member {Float32Array}
         */
        this.vertexTrimmedData = null;

        this._transformID = -1;
        this._textureID = -1;

        this._transformTrimmedID = -1;
        this._textureTrimmedID = -1;

        /**
         * Plugin that is responsible for rendering this element.
         * Allows to customize the rendering process without overriding '_draw_webgl' & '_draw_canvas' methods.
         *
         * @member {string}
         * @default 'sprite'
         */
        this.plugin_name = 'sprite';

        // call frame setter
        this.current_frame = newframe;
    }

    /**
     * When the texture is updated, this event will fire to update the scale and frame
     *
     * @private
     */
    _onTextureUpdate()
    {
        const tex = this.texture;
        this._textureID = -1;
        this._textureTrimmedID = -1;
        this.cachedTint = 0xFFFFFF;
    }

    /**
     * Called when the anchor position updates.
     *
     * @private
     */
    _onAnchorUpdate()
    {
        this._transformID = -1;
        this._transformTrimmedID = -1;
    }

    /**
     * calculates global_transform * vertices, store it in vertexData
     */
    calculateVertices()
    {
        const tex = this.texture;
        if (!tex||this._transformID === this.transform._worldID && this._textureID === tex._updateID)
        {
            return;
        }

        this._transformID = this.transform._worldID;
        this._textureID = tex._updateID;

        // set the vertex data

        const wt = this.transform.global_transform;
        const a = wt.a;
        const b = wt.b;
        const c = wt.c;
        const d = wt.d;
        const tx = wt.tx;
        const ty = wt.ty;
        const vertexData = this.vertexData;
        const trim = tex.trim;
        const orig = tex.orig;

        let w0 = 0;
        let w1 = 0;
        let h0 = 0;
        let h1 = 0;

        if (trim)
        {
            // if the sprite is trimmed and is not a tilingsprite then we need to add the extra
            // space before transforming the sprite coords.
            w1 = trim.x;
            w0 = w0 + trim.width;

            h1 = trim.y;
            h0 = h0 + trim.height;
        }
        else
        {
            w1 = orig.width;
            w0 = 0;

            h1 = orig.height;
            h0 = 0;
        }

        // xy
        vertexData[0] = (a * w1) + (c * h1) + tx;
        vertexData[1] = (d * h1) + (b * w1) + ty;

        // xy
        vertexData[2] = (a * w0) + (c * h1) + tx;
        vertexData[3] = (d * h1) + (b * w0) + ty;

         // xy
        vertexData[4] = (a * w0) + (c * h0) + tx;
        vertexData[5] = (d * h0) + (b * w0) + ty;

        // xy
        vertexData[6] = (a * w1) + (c * h0) + tx;
        vertexData[7] = (d * h0) + (b * w1) + ty;
    }

    /**
    *
    * Renders the object using the WebGL renderer
    *
    * @private
    * @param {PIXI.WebGLRenderer} renderer - The webgl renderer to use.
    */
    _on_draw_webgl(renderer)
    {
        this.calculateVertices();
        if(this.texture == null) return;

        renderer.setObjectRenderer(renderer.plugins[this.plugin_name]);
        renderer.plugins[this.plugin_name].render(this);
    }

    /**
    * Renders the object using the Canvas renderer
    *
    * @private
    * @param {PIXI.CanvasRenderer} renderer - The renderer
    */
    _on_draw_canvas(renderer)
    {
        if(this.texture)
            renderer.plugins[this.plugin_name].render(this);
    }

    /**
     * Destroys this sprite and optionally its texture and children
     *
     * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options
     *  have been set to that value
     * @param {boolean} [options.children=false] - if set to true, all the children will have their destroy
     *      method called as well. 'options' will be passed on to those calls.
     * @param {boolean} [options.texture=false] - Should it destroy the current texture of the sprite as well
     * @param {boolean} [options.baseTexture=false] - Should it destroy the base texture of the sprite as well
     */
    destroy(options)
    {
        super.destroy(options);

        const destroy_texture = typeof options === 'boolean' ? options : options && options.texture;

        if (destroy_texture)
        {
            const destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture;
            for(var i in this._textures)
                this._textures[i].destroy(!!destroyBaseTexture);
        }

        this._textures = [];
        this.shader = null;
    }

    // some helper functions..

    /**
     * Helper function that creates a new sprite based on the source you provide.
     * The source can be - frame id, image url, video url, canvas element, video element, base texture
     *
     * @static
     * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from
     * @return {PIXI.Sprite} The newly created sprite
     */
    static from_source(source)
    {
        return new Sprite(Texture.from(source));
    }

    /**
     * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId
     * The frame ids are created when a Texture packer file has been loaded
     *
     * @static
     * @param {string} frameId - The frame Id of the texture in the cache
     * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId
     */
    static from_sheet(frameId)
    {
        const texture = TextureCache[frameId];

        if (!texture)
        {
            throw new Error(`The frameId "${frameId}" does not exist in the texture cache`);
        }

        return new Sprite(texture);
    }

    /**
     * Helper function that creates a sprite that will contain a texture based on an image url
     * If the image is not in the texture cache it will be loaded
     *
     * @static
     * @param {string} imageId - The image url of the texture
     * @param {boolean} [crossorigin=(auto)] - if you want to specify the cross-origin parameter
     * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - if you want to specify the scale mode,
     *  see {@link PIXI.SCALE_MODES} for possible values
     * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id
     */
    static from_image(imageId, crossorigin, scaleMode)
    {
         return Sprite.from_texture(Texture.from_image(imageId, crossorigin, scaleMode));
    }

	/**
     * 使用指定纹理，使用网格创建对象
     * @static
     * @param {PIXI.Texture|string} tex - 纹理资源或者纹理资源名称
     * @param {number} row - 行数，默认1
     * @param {number} col - 列数，默认1
     * @param {number} begin - 起始帧，从0开始，默认0
     * @param {number} count - 多少帧
     * @return {Sprite} The new animated sprite with the specified frames.
	*/
	static from_texture(tex,row=1,col=1,begin=0,count=-1){
		if (tex instanceof Texture) {
            if(!tex.has_frame){
                 console.error('from_texture 必须指定已存在的纹理资源!!');
                 return new Sprite();
            }
			count = count<0||count+begin>col*row?col*row-begin:count
			var onew = tex.baseTexture.width / col, oneh = tex.baseTexture.height / row
			var fs = new Array(count)
			for (var ii = begin; ii < begin+count;ii++){
			  var x = ii % col
			  var y = Math.floor(ii / col)
			  fs[ii-begin] = new Texture(tex.baseTexture,new Rectangle(x*onew,y*oneh,onew,oneh),new Rectangle(0,0,onew,oneh),null,0)
			}
			return new Sprite(fs);
		} else {
		  var r = TextureCache[tex]
		  if (r) {
			return Sprite.from_texture(r, row, col,begin,count)
		  }else{
            console.error(`未找到名称为: ${tex} 的纹理资源`);
          }
		}
		return new Sprite()		
    }
    
    /**
     * A short hand way of creating a movieclip from an array of frame ids
     *
     * @static
     * @param {string[]} frames - The array of frames ids the movieclip will use as its texture frames
     * @return {Sprite} The new animated sprite with the specified frames.
     */
    static from_frames(frames)
    {
        const textures = [];

        for (let i = 0; i < frames.length; ++i)
        {
            textures.push(Texture.from_sheet(frames[i]));
        }

        return new Sprite(textures);
    }

    /**
     * A short hand way of creating a movieclip from an array of image ids
     *
     * @static
     * @param {string[]} images - the array of image urls the movieclip will use as its texture frames
     * @return {Sprite} The new animate sprite with the specified images as frames.
     */
    static from_images(images)
    {
        const textures = [];

        for (let i = 0; i < images.length; ++i)
        {
            textures.push(Texture.from_image(images[i]));
        }

        return new Sprite(textures);
    }

    /**
     * The tint applied to the sprite. This is a hex value.
     * A value of 0xFFFFFF will remove any tint effect.
     *
     * @member {number}
     * @default 0xFFFFFF
     */
    get tint()
    {
        return this._tint;
    }

    set tint(value) // eslint-disable-line require-jsdoc
    {
        this._tint = value;
        this._tintRGB = (value >> 16) + (value & 0xff00) + ((value & 0xff) << 16);
    }

    /**
     * The texture that the sprite is using
     *
     * @member {PIXI.Texture}
     */
    get texture()
    {
        return this._current_frame>=0&&this._current_frame<this._textures.length?this._textures[this._current_frame]:null;
    }

    set texture(value) // eslint-disable-line require-jsdoc
    {
        if (this.texture === value){
            return;
        }

        this._textures[this._current_frame] = value
        this.cachedTint = 0xFFFFFF;

        this._textureID = -1;
        this._textureTrimmedID = -1;

        if (value)
        {
            // wait for the texture to load
            if (value.baseTexture.hasLoaded)
            {
                this._onTextureUpdate();
            }
            else
            {
                value.once('update', this._onTextureUpdate, this);
            }
        }
    }

    /**
     * The array of frames used for this Sprite
     *
     * @member {PIXI.Texture[]}
     */
    get frames()
    {
        return this._textures;
    }

    set frames(value) // eslint-disable-line require-jsdoc
    {
        this._textures = value;
        this.current_frame = 0;
    }

    /**
    * The current frame index
    *
    * @member {number}
    * @readonly
    */
    get current_frame(){ return this._current_frame }
    set current_frame(v){
        if(v <0||v>=this._textures.length) return;
        /** fire texture setter */
        this.texture = this._textures[v];
        this._current_frame = v;
        if(!this._width&&!this._height){
            this.layout_style.width = this.texture.width;
            this.layout_style.height = this.texture.height;
        }
        this._dirty = true;
    }

    /**
    * The frame count
    *
    * @member {number}
    * @readonly
    */
    get frame_count(){ return this._textures.length }
}
